using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Xfinity.Entities;

namespace Xfinity.Engine.SceneGraph
{
    /// <summary>
    /// A node containing a list of geometry and sub-nodes.
    /// </summary>
    [Serializable]
    public class SceneNode : DrawableGameComponent
    {
        SceneNode[] nodes;
        BoundingBox bounds;
        List<IPhysicalEntity> entities;

        #region ParentNode
        private SceneNode parentNode;

        /// <summary>
        /// ParentNode.
        /// </summary>
        public SceneNode ParentNode
        {
            get { return parentNode; }
            set { parentNode = value; }
        }

        #endregion

        /// <summary>
        /// Gets the entities.
        /// </summary>
        /// <value>The entities.</value>
        public List<IPhysicalEntity> Entities
        {
            get { return entities; }
        }
        List<SceneNode> nonEmpty = new List<SceneNode>(8);
        ///// <summary>
        ///// Initializes a new instance of the <see cref="SceneNode"/> class.
        ///// </summary>
        //public SceneNode()
        //    : this(null, null, GameManager.Game)
        //{

        //}
        /// <summary>
        /// Initializes a new instance of the <see cref="SceneNode"/> class.
        /// </summary>
        /// <param name="models">The models.</param>
        public SceneNode(IEnumerable<IPhysicalEntity> models, SceneNode parent, Game game):base(game)
        {
            if (models != null)
            {
                entities = new List<IPhysicalEntity>(models);
            }
            else
            {
                entities = new List<IPhysicalEntity>();
            }
            parentNode = parent;
        }

        private void InitializeBoundingBox()
        {
            BoundingBox box = new BoundingBox();

            foreach (PhysicalEntity entity in entities)
            {
                Model model = entity.Model;
                if (model == null)
                {
                    bounds = new BoundingBox();
                    return;
                }
                Matrix[] transforms = new Matrix[model.Bones.Count];
                model.CopyAbsoluteBoneTransformsTo(transforms);

                //box = BoundingBox.CreateMerged(box,BoundingBox.CreateFromSphere(entity.Model.bou
                for (int i = 0; i < model.Meshes.Count; i++)
                {
                    ModelMesh mesh = model.Meshes[i];
                    BoundingSphere s = new BoundingSphere(transforms[mesh.ParentBone.Index].Translation + mesh.BoundingSphere.Center, mesh.BoundingSphere.Radius);
                    box = BoundingBox.CreateMerged(box, BoundingBox.CreateFromSphere(s));
                }
            }
            bounds = box;
        }
        public void Add(PhysicalEntity ent)
        {
            PushDownEntityToLowestNode(ent);
        }
        protected override void LoadGraphicsContent(bool loadAllContent)
        {
            foreach (IPhysicalEntity entity in this.entities)
            {
                entity.LoadGraphics(loadAllContent);
            }
            if (nodes != null)
            {
                foreach (SceneNode node in nodes)
                {
                    node.LoadGraphicsContent(loadAllContent);
                }
            }
            base.LoadGraphicsContent(loadAllContent);
        }
        private void PushDownEntityToLowestNode(PhysicalEntity ent)
        {
            bool transferred = false;
            if (nodes == null || nodes.Length == 0)
            {
                goto End;
            }
            foreach (SceneNode node in nodes)
            {
                if (TestContainment(ent, node))
                {
                    TransferOwnership(node, ent);
                    transferred = true;
                    break;
                }
            }
        End:
            if (!transferred)
            {
                entities.Add(ent);
            }
        }

        /// <summary>
        /// Subdivides this node.
        /// </summary>
        public void Subdivide()
        {
            //check to see if Subdivide has already called, or if it's an empty node.
            if (nodes != null && nodes.Length == 0)
            {
                return;
            }

            if (entities.Count <= SceneGraph.MaxModelsPerNode)
            {
                CleanUpEmptyNodes();
                return;
            }
            nodes = new SceneNode[8];
            for (int i = 0; i < 8; i++)
            {
                nodes[i] = new SceneNode(null,this,Game);
            }

            //get center of the node.
            Vector3 centerOfBounds = ((bounds.Max + bounds.Min) / 2);

            Vector3[] corners = bounds.GetCorners();

            for (int i = 0; i < 8; i++)
            {
                nodes[i].bounds = new BoundingBox(centerOfBounds, corners[i]);
            }

            //if a mesh is completely inside a node, add it to the node and remove it from the root.
            foreach (PhysicalEntity ent in entities)
            {
                FindBoundingSubNode(ent);
            }

            CleanUpEmptyNodes();

            if (nonEmpty.Count > 0)
            {
                foreach (SceneNode node in nonEmpty)
                {
                    node.Initialize();
                }
            }
        }

        private void FindBoundingSubNode(PhysicalEntity ent)
        {
            for (int i = 0; i < nodes.Length; i++)
            {
                if (TestContainment(ent, nodes[i]))
                {
                    TransferOwnership(nodes[i], ent);
                    break;
                }
            }
        }

        private bool TestContainment(PhysicalEntity ent, SceneNode node)
        {
            bool contained = true;
            foreach (ModelMesh mesh in ent.Model.Meshes)
            {
                if (node.bounds.Contains(mesh.BoundingSphere) != ContainmentType.Contains)
                {
                    contained = false;
                    break;
                }
            }

            return contained;
        }

        private void CleanUpEmptyNodes()
        {
            //remove any nodes that are empty
            for (int i = 0; i < 8; i++)
            {
                if (nodes[i].entities.Count == 0)
                {
                    nodes[i] = null;
                }
                else
                {
                    nonEmpty.Add(nodes[i]);
                }
            }

            //trim excess fat
            nonEmpty.TrimExcess();
            if (nonEmpty.Count == 0)
            {
                nodes = null;
                nonEmpty = null;
            }
        }
        
        /// <summary>
        /// Draws the IDrawable.
        /// </summary>
        /// <param name="gameTime">Snapshot of the game's timing state.</param>
        public void Draw(GameTime gameTime)
        {
            GraphicsHelper.DrawClippedModel(entities);
            if (nonEmpty != null)
            {
                foreach (SceneNode node in nonEmpty)
                {
                    if (GraphicsHelper.ActiveCamera.Frustum.Intersects(node.bounds))
                    {
                        node.Draw(gameTime);
                    }
                }
            }
        }

        /// <summary>
        /// Transfers the ownership.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <param name="ent">The entity.</param>
        private void TransferOwnership(SceneNode node, PhysicalEntity ent)
        {
            node.entities.Add(ent);
            if (entities.Contains(ent))
            {
                entities.Remove(ent);
            }
        }

        #region IDrawable Members

        int drawOrder;
        bool visible;

        /// <summary>
        /// The order to draw this object in relative to other objects.  Object with a lower value are drawn first.
        /// </summary>
        /// <value></value>
        /// <returns>Order to draw this object in relative to other objects.</returns>
        public int DrawOrder
        {
            set
            {
                drawOrder = value;
                EventHandler temp = DrawOrderChanged;
                if (temp != null)
                {
                    temp(this, EventArgs.Empty);
                }
            }
            get { return drawOrder; }
        }

        /// <summary>
        /// Fired when the draw order changes.
        /// </summary>
        public event EventHandler DrawOrderChanged;

        /// <summary>
        /// Indicates whether IDrawable.Draw should be called in Game.Draw for this game component.
        /// </summary>
        /// <value></value>
        /// <returns>true if IDrawable.Draw should be called; false otherwise.
        /// </returns>
        public bool Visible
        {
            set
            {
                visible = value;
                EventHandler temp = VisibleChanged;
                if (temp != null)
                {
                    temp(this, EventArgs.Empty);
                }
            }
            get { return visible; }
        }
        /// <summary>
        /// Fires when the visibility changes.
        /// </summary>
        public event EventHandler VisibleChanged;

        #endregion

        #region IGameComponent Members

        /// <summary>
        /// Called when the component should be initialized.
        /// This method can be used for tasks like querying for services the component needs and setting up non-graphics resources.
        /// </summary>
        public void Initialize()
        {
            InitializeBoundingBox();
            if (bounds.Max-bounds.Min == Vector3.Zero)
            {
                return;
            }
            if (entities.Count != 0)
            {
                Subdivide();
            }
        }
        #endregion


        public void LoadGraphics(bool loadAllContent)
        {
            LoadGraphicsContent(loadAllContent);
        }
    }
}